﻿
using System;
using System.Collections.Generic;
using System.Reflection;
using AutoMapper;
using NHibernate;
using RBAC.DAL.Entities;
using RBAC.DAL.Models;
using RBAC.Service.Dto;

namespace RBAC.Service.Util
{
    public class MapperUtil
    {
        private const string HeadGenericFullName = "System.Collections.Generic.IList`1[[";
        private const string TailGenericFullName = "]]";
        private readonly Type _typeOfBaseSrce;
        private readonly string _tailOfDestClass;
        private readonly List<string> _listOfAssemblyQualifiedNameOfSource;
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="typeOfBaseSrce">Baseclass Type Of Source</param>
        /// <param name="typeOfBaseDest">Baseclass Type Of Destination</param>
        public MapperUtil(Type typeOfBaseSrce, Type typeOfBaseDest)
        {
            _typeOfBaseSrce = typeOfBaseSrce;
            _tailOfDestClass = GetTailOfClass(typeOfBaseDest);
            _listOfAssemblyQualifiedNameOfSource = new List<string>();
        }
        /// <summary>
        /// Config AutoMapper.Mapper.CreateMap(Source, Destination);
        /// </summary>
        /// <param name="typeOfSource">Type Of Source</param>
        /// <param name="typeOfDest">Type Of Destination</param>
        public void MapperCreateMap(Type typeOfSource, Type typeOfDest)
        {
            var exist = _listOfAssemblyQualifiedNameOfSource.Exists(s => s == typeOfSource.AssemblyQualifiedName);
            if (exist) return;

            Mapper.CreateMap(typeOfSource, typeOfDest);
            _listOfAssemblyQualifiedNameOfSource.Add(typeOfSource.AssemblyQualifiedName);

            var assemblyNameOfDest = typeOfDest.Assembly.FullName;
            var namespaceOfDest = typeOfDest.Namespace;

            foreach (var property in typeOfSource.GetProperties())
            {
                var assemblyQualifiedNameOfSource = property.PropertyType.AssemblyQualifiedName;
                var isInheritFromBaseSrce = property.PropertyType.IsSubclassOf(_typeOfBaseSrce);

                var propertyName = property.Name;
                if (property.PropertyType.IsGenericType)
                {
                    if (!string.IsNullOrEmpty(property.PropertyType.FullName))
                    {
                        assemblyQualifiedNameOfSource = property.PropertyType.FullName.Replace(HeadGenericFullName, string.Empty);
                        assemblyQualifiedNameOfSource = assemblyQualifiedNameOfSource.Replace(TailGenericFullName, string.Empty);
                        propertyName = propertyName.Remove(propertyName.Length - 1);

                        var type = Type.GetType(assemblyQualifiedNameOfSource);
                        if (type != null)
                            isInheritFromBaseSrce = type.IsSubclassOf(_typeOfBaseSrce);
                    }
                }

                var isGenericTypeThatInheritFromBaseSrce = property.PropertyType.IsGenericType && isInheritFromBaseSrce;
                if (!isGenericTypeThatInheritFromBaseSrce && !isInheritFromBaseSrce) continue;

                //var classNameOfDest = string.Format("{0}{1}", propertyName, _tailOfDestClass);
				//var classNameOfDest = typeOfDest.Name;
				var classNameOfDest = property.GetType();
                var assemblyQualifiedNameOfDest = string.Format("{0}.{1}, {2}",
                                                                namespaceOfDest,
                                                                classNameOfDest,
                                                                assemblyNameOfDest);

                if (assemblyQualifiedNameOfSource == null) continue;

                var typeSource = Type.GetType(assemblyQualifiedNameOfSource);
                var typeDestination = Type.GetType(assemblyQualifiedNameOfDest);
                MapperCreateMap(typeSource, typeDestination);
            }
        }

        public TDestination Map<TSource, TDestination>(TSource source)
        {
            var typeOfSource = typeof(TSource);
            var typeOfDest = typeof(TDestination);

            var exist = _listOfAssemblyQualifiedNameOfSource.Exists(s => s == typeOfSource.AssemblyQualifiedName);
            if (!exist)
            {
                Mapper.CreateMap(typeOfSource, typeOfDest);
                _listOfAssemblyQualifiedNameOfSource.Add(typeOfSource.AssemblyQualifiedName);

                var assemblyNameOfDest = typeOfDest.Assembly.FullName;
                var namespaceOfDest = typeOfDest.Namespace;

                foreach (var property in typeOfSource.GetProperties())
                {
                    var assemblyQualifiedNameOfSource = property.PropertyType.AssemblyQualifiedName;
                    var isInheritFromBaseSrce = property.PropertyType.IsSubclassOf(_typeOfBaseSrce);

                    var propertyName = property.Name;
                    if (property.PropertyType.IsGenericType)
                    {
                        if (!string.IsNullOrEmpty(property.PropertyType.FullName))
                        {
                            assemblyQualifiedNameOfSource = property.PropertyType.FullName.Replace(HeadGenericFullName,
                                                                                                   string.Empty);
                            assemblyQualifiedNameOfSource = assemblyQualifiedNameOfSource.Replace(TailGenericFullName,
                                                                                                  string.Empty);
                            propertyName = propertyName.Remove(propertyName.Length - 1);

                            var type = Type.GetType(assemblyQualifiedNameOfSource);
                            if (type != null)
                                isInheritFromBaseSrce = type.IsSubclassOf(_typeOfBaseSrce);
                        }
                    }

                    var isGenericTypeThatInheritFromBaseSrce = property.PropertyType.IsGenericType &&
                                                               isInheritFromBaseSrce;
                    if (!isGenericTypeThatInheritFromBaseSrce && !isInheritFromBaseSrce) continue;

                    //var classNameOfDest = string.Format("{0}{1}", propertyName, _tailOfDestClass);
					//var classNameOfDest = typeOfDest.Name;
					var classNameOfDest = property.GetType();
                    var assemblyQualifiedNameOfDest = string.Format("{0}.{1}, {2}",
                                                                    namespaceOfDest,
                                                                    classNameOfDest,
                                                                    assemblyNameOfDest);

                    if (assemblyQualifiedNameOfSource == null) continue;

                    var typeSource = Type.GetType(assemblyQualifiedNameOfSource);
                    var typeDestination = Type.GetType(assemblyQualifiedNameOfDest);
                    MapperCreateMap(typeSource, typeDestination);
                }
            }
            var result = Mapper.Map<TSource, TDestination>(source);
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        private static string GetTailOfClass(Type typeOfClass)
        {
            if (typeof(BaseEntity) == typeOfClass) return string.Empty;

            if (typeof(BaseDto) == typeOfClass) return "Dto";

            if (typeof(BaseSearch) == typeOfClass) return "Search";

            return string.Empty;
        }

        public TDto GetDto<TDto, TEntity>(ISession session, TDto dto)
        {
            var typeOfDto = dto.GetType();
            var infoIdOfDto = typeOfDto.GetProperty("Id");

            var resultOfEntity = session.Get<TEntity>(infoIdOfDto.GetValue(dto, null));
            var resultOfDto = Map<TEntity, TDto>(resultOfEntity);

            //MapperCreateMap(resultOfEntity.GetType(), dto.GetType());
            //var resultOfDto = Mapper.Map<TEntity, TDto>(resultOfEntity);

            //var entity = _session.Get<EntityClass>(BaseDto.Id);
            //.MapperCreateMap(EntityClass, DtoClass);
            //var dto = Mapper.Map<EntityClass, DtoClass>(entity);

            return resultOfDto;
        }
    }

    [Serializable]
    public class ProperityNotFoundException : Exception
    {
        public ProperityNotFoundException()
        { }
        public ProperityNotFoundException(string message) : base(message) { }
    }
}
